import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * The data class representing an instruction in the Listing.
 * 
 * An instruction is the logical representation of one line of the input. All
 * data on the line (including comments) is included, so the listing file can be
 * reproduced. Note that Instruction is used for directives as well as true
 * instructions.
 * 
 * The main functionality provided by an instruction is code generation, via the
 * getBinary() method. This method will convert the instruction into a 32-bit
 * Integer, using the supplied operands. For directives that don't actually
 * generate code (eg KICKO or NEWLC), this method should return null. Such
 * Instructions should also implement the Instruction.Directive interface, which
 * exposes a process(IListing, ISymTable) method to allow them to perform
 * arbitrary actions.
 * 
 * @author Andrew Krieger
 * @version 0.1
 * @since 6 Apr 2012
 */
public abstract class Instruction {
	/**
	 * Interface extending a directive to add arbitrary functionality
	 * 
	 * Directives can do effectively anything, like mess with the locarion
	 * counter or define symbols. Let's make things easy on ourselves by letting
	 * us define custom code for each one.
	 */
	public static interface Directive {
		/**
		 * Perform the action defined by the directive
		 * 
		 * @param lst
		 *            The current listing object
		 * @param sym
		 *            The current symbol table
		 */
		public void process(IListing lst, ISymbolTable sym);
	}

	/**
	 * Initialize an instruction with the given arguments. The biggest thing
	 * this does is initialize the Operand list.
	 * 
	 * @param line
	 *            The current line number
	 * @param label
	 *            The label associated with this instruction
	 * @param comment
	 *            The comment after the semicolon
	 * @param mnemonic
	 *            The mnemonic on this line, or NONE of the line contains only a
	 *            comment
	 * @param ops
	 *            A list of operands supplied on the line
	 */
	protected Instruction(int line, String label, String comment,
			Mnemonic mnemonic, Iterable<Operand> ops) {
		this.line = line;
		this.label = label;
		this.comment = comment;
		this.mnemonic = mnemonic;

		ArrayList<Operand> operandList = new ArrayList<Operand>();
		for (Operand o : ops) {
			operandList.add(o);
		}

		this.ops = Collections.unmodifiableList(operandList);
	}

	/**
	 * Generate code for this instruction. This combine the mnemonic and
	 * operands as-needed to generate a 32-bit code word.
	 * 
	 * @returns An integer containing the code value, or null if this
	 *          instruction is actually a Directive that does not generate code.
	 */
	abstract public Integer getBinary();

	/**
	 * Get the operand associated with a given keyword, and assert it's unique
	 * 
	 * Look up an operand by name. If that operand was not used in this
	 * particular instruction, returns null. If it was supplied exactly once,
	 * return that instance. If more than one value was supplied, raise an
	 * IllegalStateException.
	 * 
	 * @param kw
	 *            An operand keyword to match
	 * @return The operand with the right keyword, or null
	 * @throws Exception
	 *             If the value is not uniquely defined
	 */
	protected Operand getUniqueOperand(Operand.Keyword kw) throws Exception {
		Operand ret = null;
		for (Operand op : ops) {
			if (op.getKeyword() == kw) {
				if (ret == null) {
					ret = op;
				} else {
					throw new Exception("Duplicate operands for " + kw);
				}
			}
		}
		return ret;
	}

	/**
	 * Get all operands of a given type.
	 * 
	 * Return an iterator over all the operands of the supplied keyword. This
	 * will work whether 0, 1, or many with that keyword were provided.
	 * 
	 * @param kw
	 *            A operand keyword to match
	 * @return An iterator over the operands with the supplied keyword
	 */
	protected Collection<Operand> getOperands(Operand.Keyword kw) {
		Collection<Operand> ret = new ArrayList<Operand>();
		for (Operand op : ops) {
			if (op.getKeyword() == kw) {
				ret.add(op);
			}
		}
		return ret;
	}

	// [line The current line number]
	protected final int line;
	// [label The label on this line, or null if there is none]
	protected final String label;
	// [comment The comment on this line, or null if there is none]
	protected final String comment;
	// [mnemonic The mnemonic on this line, or null for comment lines]
	protected final Mnemonic mnemonic;
	// [ops List of operands supplied to this instructions]
	protected final List<Operand> ops;
}
